package com.heo.server;

import java.io.File;
import java.net.InetSocketAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.heo.pojo.DeviceChannelEntity;
import com.heo.pojo.DeviceInfoEntity;
import com.heo.pojo.DeviceModelEntity;
import com.heo.pojo.DeviceReceivePacketHistoryEntity;
import com.heo.pojo.OrgInfoEntity;
import com.heo.protocol.packet.BasePacket;
import com.heo.protocol.packet.DeviceCommand;
import com.heo.protocol.packet.EditDevicePasswordReq;
import com.heo.protocol.packet.EditDevicePasswordResp;
import com.heo.protocol.packet.FirmwareDownloadReq;
import com.heo.protocol.packet.FirmwareDownloadResp;
import com.heo.protocol.packet.GetDebugServerListReq;
import com.heo.protocol.packet.GetDebugServerListResp;
import com.heo.protocol.packet.GetDeviceCodeReq;
import com.heo.protocol.packet.GetDeviceCodeResp;
import com.heo.protocol.packet.GetDeviceParaReq;
import com.heo.protocol.packet.GetDeviceParaResp;
import com.heo.protocol.packet.GetServerListReq;
import com.heo.protocol.packet.GetServerListResp;
import com.heo.protocol.packet.HeartBySignalReq;
import com.heo.protocol.packet.HeartBySignalResp;
import com.heo.protocol.packet.HeartReq;
import com.heo.protocol.packet.HeartResp;
import com.heo.protocol.packet.LoginOutReq;
import com.heo.protocol.packet.LoginOutResp;
import com.heo.protocol.packet.LoginReq;
import com.heo.protocol.packet.LoginResp;
import com.heo.protocol.packet.PutCoinReq;
import com.heo.protocol.packet.PutCoinResp;
import com.heo.protocol.packet.ReadDeviceCodeReq;
import com.heo.protocol.packet.ReadDeviceCodeResp;
import com.heo.protocol.packet.RebootDeviceResp;
import com.heo.protocol.packet.ReportDeviceStatusReq;
import com.heo.protocol.packet.ReportDeviceStatusResp;
import com.heo.protocol.packet.SocketPacket;
import com.heo.protocol.packet.StartDeviceResp;
import com.heo.protocol.packet.UpdateDeviceParaResp;
import com.heo.protocol.packet.UploadLogReq;
import com.heo.protocol.packet.UploadLogResp;
import com.heo.pub.ApplicationContextUtil;
import com.heo.pub.DictEnum;
import com.heo.pub.ServerConfig;
import com.heo.queue.TaskQueue;
import com.heo.queue.task.CallBackTask;
import com.heo.queue.task.ReSendResponseInfo;
import com.heo.queue.task.ReceiveTask;
import com.heo.service.DeviceChannelService;
import com.heo.service.DeviceInfoService;
import com.heo.service.DeviceModelService;
import com.heo.service.DeviceReceivePacketHistoryService;
import com.heo.service.OrgInfoService;
import com.heo.util.ByteHelper;
import com.heo.util.FileHelper;
import com.heo.util.RandomNumHelper;
import com.heo.util.TimeHelper;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;

/**
 * Created by zhangcf handler不是一个单例.即每个channel下都会有自己的一个handler实例.
 * 对于使用spring注解时，一定要注解该类为多例。否则通道断开再重新建立时，会提示 NettyServerHandler is not
 * a @Sharable handler, so can't be added or removed multiple times.
 */
@Component
@Scope("prototype")
public class SocketServerHandler extends ChannelInboundHandlerAdapter
{
  private Logger log = Logger.getLogger(this.getClass());
  private String logHeader = "";
  private boolean isLock = true;// 设备第一次登录时，需要修改自己的初始密码，才可以做剩余的操作。所以默认情况下是锁定的。

  private DeviceInfoEntity currDeviceInfoEntity = null;// 当前链路的设备信息
  private OrgInfoEntity currOrgInfoEntity = null;// 当前设备对应的商户信息(设备属于哪个商户使用)

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
  {
    log.error(logHeader + "[断开]操作异常，socket连接已断开。");
    log.error(cause.getMessage(), cause);
    // 网络异常，断开。
    ctx.close();
  }

  /**
   * 客户端连接上来的时候触发 连接上来的时候，并不知道客户端是谁，需要根据他后面发的指令才知道是谁。
   */
  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception
  {
    String channelId = ctx.channel().id().asShortText();
    String remoteIpAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
    logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]";
    log.info(logHeader + "[连接]与客户端建立连接成功。");
  }

  /**
   * 连接关闭成功后触发
   */
  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception
  {
    String channelId = ctx.channel().id().asShortText();

    // 仅当设备登录成功后，才会有currDeviceInfoEntity对象。所以如果未登录成功的情况下出异常，这里不做remove的操作是没有问题的。
    if (currDeviceInfoEntity != null)
    {
      // 当前链路关闭的时候，检查内存中记录的链路ID和自己当前的链路ID是否一致。如果不一致，说明已经有新的链路登录上来了。
      // 此时只是把当前链路关闭即可，千万不能把该链路从内存中移除，否则会造成后续处理获取不到链路的异常。
      if (channelId.equals(SocketChannelMap.getSocketChannelIdByIMEI(currDeviceInfoEntity.getDeviceImei())))
      {
        SocketChannelMap.remove(currDeviceInfoEntity.getDeviceImei());
        // 不能断开时释放，因为下次登录的时候，还需要使用上一次的ID继续编号。
        // SequenceHelper.removeId(currDeviceInfoEntity.getDeviceImei());
        // 把序列id给清除掉。释放内存。
        log.debug(logHeader + "[断开]链路对象已从内存移除。");
      }
      else
      {
        log.debug(logHeader + "[断开]当前设备有新的链路登录成功，链路对象在内存中已更新。移除失败。");
      }
      log.info(logHeader + "[断开]设备<" + currDeviceInfoEntity.getDeviceImei() + ">，机构<" + currOrgInfoEntity.getOrgId() + ">，socket连接已断开。");
    }
    else
    {
      log.info(logHeader + "[断开]socket连接已断开。");
    }
  }

  /**
   * 收到客户端传上来的数据触发
   */
  @SuppressWarnings("unused")
  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
  {
    SocketPacket body = (SocketPacket) msg;
    try
    {
      String channelId = ctx.channel().id().asShortText();

      log.debug(logHeader + "[请求]收到消息：" + body.toString());

      int commandId = body.getCommandId();

      // 校验用户是否已经登录成功
      if (commandId != DeviceCommand.LOGIN_REQ && currDeviceInfoEntity == null)
      {
        // 说明用户没有登录，此时统一返回退出登录，然后断开连接
        LoginOutResp loginOutResp = new LoginOutResp(DictEnum.ResultCode.NOT_LOGIN, (short) body.getSequenceId());// 退出登录应答包
        sendPacket(ctx.channel(), loginOutResp);
        log.debug(logHeader + "[应答]当前连接未登录，断开连接。");
        ctx.close();
        return;
      }

      // 判断用户是否在锁定状态
      if (isLock == true)
      {
        if (!(commandId == DeviceCommand.LOGIN_REQ || commandId == DeviceCommand.EDIT_LOGIN_PASSWORD_REQ || commandId == DeviceCommand.LOGINOUT_REQ))
        {
          // 说明设备没有修改过密码，因此无法使用其他功能
          LoginOutResp loginOutResp = new LoginOutResp(DictEnum.ResultCode.DEVICE_IS_LOCK, (short) body.getSequenceId());// 退出登录应答包
          sendPacket(ctx.channel(), loginOutResp);
          log.debug(logHeader + "[应答]当前设备处于锁定状态，不能使用除登录、退出登录、修改密码之外的任何指令。断开连接。");
          ctx.close();
          return;
        }
      }

      switch (commandId)
      {
        // 登录
        case DeviceCommand.LOGIN_REQ:
          log.debug(logHeader + "[请求]收到登录请求包。");
          executeLogin(ctx, body);
          break;

        // 退出登录
        case DeviceCommand.LOGINOUT_REQ:
          log.debug(logHeader + "[请求]收到退出登录请求包。");
          executeLoginOut(ctx, body);
          ctx.close();
          break;

        // 修改设备密码
        case DeviceCommand.EDIT_LOGIN_PASSWORD_REQ:
          log.debug(logHeader + "[请求]收到修改设备密码请求包。");
          editDevicePassword(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 心跳
        case DeviceCommand.HEART_REQ:
          log.debug(logHeader + "[请求]收到设备心跳包。");
          executeHeart(ctx, body);
          // 心跳请求过来的时候，并不是刷新设备的状态。而是延长在redis里存储的时间。设备的状态是由状态上报接口来完成的。这里直接从当前链路中，把设备的信息取到就可以了。因为只有登录成功后才可以有这个设备的对象
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());
          break;

        // 带信号强度的心跳
        case DeviceCommand.HEART_BY_SINGAL_REQ:
          log.debug(logHeader + "[请求]收到带信号强度的设备心跳包。");
          executeHeartBySignal(ctx, body);
          //更新过redis里的数据，所以不需要再到redis里延期
          break;

        // 投币请求
        case DeviceCommand.PUT_COIN_REQ:
          log.debug(logHeader + "[请求]收到投币消息包。");
          executePutCoin(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到服务器要求设备上传日志的命令响应
        case DeviceCommand.SET_UPLOAD_LOG_RESP:
          log.debug(logHeader + "[请求]收到请求设备上传日志消息应答包。");
          executeSetUpload(channelId, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到要求设备更新参数指令应答包
        case DeviceCommand.UPDATE_DEVICE_PARA_RESP:
          log.debug(logHeader + "[请求]收到要求设备更新参数应答包。");
          executeUpdateDeviceParaResp(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 设备请求获取运行参数
        case DeviceCommand.GET_DEVICE_PARA_REQ:
          log.debug(logHeader + "[请求]收到获取设备参数请求包。");
          executeGetDeviceParaReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 设备请求获取服务器列表
        case DeviceCommand.GET_SERVER_LIST_REQ:
          log.debug(logHeader + "[请求]收到获取服务器列表参数请求包。");
          executeGetServerListReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 设备请求获取调试服务器列表
        case DeviceCommand.GET_DEBUG_SERVER_LIST_REQ:
          log.debug(logHeader + "[请求]收到获取调试服务器列表参数请求包。");
          executeGetDebugServerListReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 报告设备状态请求
        case DeviceCommand.REPORT_DEVICE_STATUS_REQ:
          log.debug(logHeader + "[请求]收到报告设备状态请求包。");
          executeReportDeviceStatusReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到设备启动应答，需要做重发处理
        case DeviceCommand.START_DEVICE_RESP:
          log.debug(logHeader + "[请求]收到设备启动应答包。");
          executeStartDeviceResp(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到设备重启应答
        case DeviceCommand.REBOOT_DEVICE_RESP:
          log.debug(logHeader + "[请求]收到设备重启应答包。");
          executeRebootDeviceResp(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到日志上传请求（设备要求上传自己的日志，由设备发起）
        case DeviceCommand.UPLOAD_LOG_REQ:
          log.debug(logHeader + "[请求]收到设备上传日志请求包。");
          executeUploadDeviceLogReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到固件升级通知应答
        case DeviceCommand.FIRMWARE_NOTICE_RESP:
          log.debug(logHeader + "[请求]收到设备固件升级通知应答包。");
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到固件下载请求
        case DeviceCommand.FIRMWARE_DOWNLOAD_REQ:
          log.debug(logHeader + "[请求]收到固件下载请求包。");
          executeFirmwareDownloadReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到获取设备二维码请求
        case DeviceCommand.GET_DEVICE_CODE_REQ:
          log.debug(logHeader + "[请求]收到获取设备二维码请求包。");
          executeGetDeviceCodeReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        // 收到设备反向扫码请求
        case DeviceCommand.READ_DEVICE_CODE_REQ:
          log.debug(logHeader + "[请求]收到设备反向扫码请求包。");
          executeReadDeviceCodeReq(ctx, body);
          // 刷新设备在redis里的过期时间
          SocketChannelMap.refreshDeviceControlInfoExpire(currDeviceInfoEntity.getDeviceImei());// 设备有消息上来，在redis里做延期
          break;

        default:
          // 消息指令不对，断开连接
          log.debug(logHeader + "[请求]消息指令不存在，断开链路。");
          LoginOutResp loginOutResp = new LoginOutResp(DictEnum.ResultCode.COMMAND_ERROR, (short) body.getSequenceId());// 退出登录应答包
          sendPacket(ctx.channel(), loginOutResp);
          ctx.close();
          break;
      }
    }
    catch (Exception e)
    {
      LoginOutResp loginOutResp = new LoginOutResp(DictEnum.ResultCode.STRUCT_ERROR, (short) body.getSequenceId());// 退出登录应答包
      sendPacket(ctx.channel(), loginOutResp);
      ctx.close();
      log.error(e.getMessage(), e);
      throw e;
    }
    finally
    {
      // 对于不再使用的ByteBuf引用计数类型，需要手动释放。
      // http://netty.io/wiki/reference-counted-objects.html
      // 当有写操作时，不需要手动释放msg的引用
      // 当只有读操作时，才需要手动释放msg的引用
      // ReferenceCountUtil.release(msg);
      ReferenceCountUtil.release(msg);
    }
  }

  /**
   * 每次达到心跳IdleStateHandler设定的超时时间就会触发
   */
  @Override
  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception
  {
    if (evt instanceof IdleStateEvent)
    {
      IdleStateEvent e = (IdleStateEvent) evt;
      // 断开连接
      ctx.channel().close().sync();
      switch (e.state())
      {
        case READER_IDLE:
          log.info(logHeader + "[应答]socket read timeout. bye.");
          break;
        case WRITER_IDLE:
          log.info(logHeader + "[应答]socket write timeout. bye.");
          break;
        case ALL_IDLE:
          log.info(logHeader + "[应答]socket all timeout. bye.");
          break;
        default:
          break;
      }
    }
  }

  /**
   * 固件下载请求
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeFirmwareDownloadReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      FirmwareDownloadReq fdr = new FirmwareDownloadReq(logHeader, body);// 获得设备请求包

      FirmwareDownloadResp fdrp = new FirmwareDownloadResp((short) body.getSequenceId());// 生成应答包

      // 根据请求中文件的要求，获得需要的文件字节流
      int startIndex = fdr.getStartIndex();// 从哪个位置开始获取
      int downLength = fdr.getDownLength();// 要获取的文件长度
      if (downLength > (0xFFFF - 0x0007))
      {
        // 请求下载的文件内容长度，不可以超过2个字节所能表达的最大长度 - 包头的长度（7个字节）
        log.error(logHeader + "[应答]请求固件下载的长度参数错误，当前请求下载的长度为：" + downLength);
        fdrp.setFileContentArray(null);
        sendPacket(ctx.channel(), fdrp);
        return;
      }
      // 读取文件
      // 查询出设备的升级固件信息
      DeviceModelService service = (DeviceModelService) ApplicationContextUtil.getBeanById("deviceModelService");
      HashMap<String, Object> paraMap = new HashMap<String, Object>();
      paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());

      DeviceModelEntity deviceModel = service.getByMap(paraMap, "getByDeviceIMEI");
      String firmwarePath = deviceModel.getFirmwareUrl();
      if (firmwarePath == null)
      {
        // 请求下载的文件不存在
        log.error(logHeader + "[应答]请求下载的固件不存在。请求路径：" + firmwarePath);
        fdrp.setFileContentArray(null);
        sendPacket(ctx.channel(), fdrp);
        return;
      }
      File firmwareFile = FileHelper.getFile(ServerConfig.FIRMWARE_DIRECTORY + "/" + firmwarePath);
      if (firmwareFile == null)
      {
        // 请求下载的文件不存在
        log.error(logHeader + "[应答]请求下载的固件不存在。请求路径：" + ServerConfig.FIRMWARE_DIRECTORY + "/" + firmwarePath);
        fdrp.setFileContentArray(null);
        sendPacket(ctx.channel(), fdrp);
        return;
      }
      // 读取文件
      byte[] fileArray = FileHelper.getFileArray(firmwareFile, startIndex, downLength);
      fdrp.setFileContentArray(fileArray);
      // 发送下载数据流
      log.debug(logHeader + "[应答]请求下载的固件成功。偏移量：" + startIndex + "，下载长度：" + downLength + "，下载路径：" + ServerConfig.FIRMWARE_DIRECTORY + "/" + firmwarePath);
      sendPacket(ctx.channel(), fdrp);
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 设备获取二维码请求
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeGetDeviceCodeReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    GetDeviceCodeResp gdcrsp = null;
    try
    {
      GetDeviceCodeReq gdcr = new GetDeviceCodeReq(logHeader, body);// 获得设备请求包

      gdcrsp = new GetDeviceCodeResp((short) body.getSequenceId());

      // 去数据库查找该设备的通道二维码
      DeviceChannelService service = (DeviceChannelService) ApplicationContextUtil.getBeanById("deviceChannelService");
      HashMap<String, Object> paraMap = new HashMap<String, Object>();
      paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());
      paraMap.put("deviceChannelId", gdcr.getChannelIndex());
      DeviceChannelEntity deviceChannelEntity = service.getByMap(paraMap, "getDeviceByIMEIAndChannelId");
      if (deviceChannelEntity == null)
      {
        log.debug(logHeader + "[应答]获取设备的二维码错误：设备或通道不存在。");
        gdcrsp.setResult(DictEnum.GetDeviceCodeResultCode.DEVICE_OR_CHANNEL_IS_NOT_EXIST);// 设备或通道不存在
      }
      else if (deviceChannelEntity.getDeviceInfoEntity().getDeviceStatus() == 1)
      {
        log.debug(logHeader + "[应答]获取设备的二维码错误：设备已停用。");
        gdcrsp.setResult(DictEnum.GetDeviceCodeResultCode.DEVICE_IS_STOP);// 设备已停用
      }
      else if (deviceChannelEntity.getDeviceChannelStatus() == 0)
      {
        log.debug(logHeader + "[应答]获取设备的二维码错误：操作成功。");
        gdcrsp.setResult(DictEnum.GetDeviceCodeResultCode.SUCCESS);// 操作成功
        gdcrsp.setDeviceCode(deviceChannelEntity.getDeviceChannelCode());// 设置二维码参数
      }
      else if (deviceChannelEntity.getDeviceChannelStatus() == 1)
      {
        log.debug(logHeader + "[应答]获取设备的二维码错误：通道被停用。");
        gdcrsp.setResult(DictEnum.GetDeviceCodeResultCode.DEVICE_CHANNEL_IS_STOP);// 通道被停用
      }
      else
      {
        log.debug(logHeader + "[应答]获取设备的二维码错误：未知错误。");
        gdcrsp.setResult(DictEnum.GetDeviceCodeResultCode.UNKNOW_ERROR);// 未知错误
      }
    }
    catch (Exception e)
    {
      throw e;
    }
    log.debug(logHeader + "[应答]向设备发送二维码数据。");
    sendPacket(ctx.channel(), gdcrsp);
  }

  /**
   * 收到设备反向扫码请求
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeReadDeviceCodeReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    ReadDeviceCodeReq gdcr = null;
    ReadDeviceCodeResp rdcrsp = null;
    try
    {
      gdcr = new ReadDeviceCodeReq(logHeader, body);// 获得设备请求包

      rdcrsp = new ReadDeviceCodeResp((short) body.getSequenceId());

      // 暂时未实现设备反向扫码的结果进行回调

    }
    catch (Exception e)
    {
      throw e;
    }
    log.debug(logHeader + "[应答]收到反向扫码数据。内容：" + gdcr.toString());
    sendPacket(ctx.channel(), rdcrsp);
  }

  /**
   * 收到上传日志请求
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeUploadDeviceLogReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    String currChannelId = ctx.channel().id().asShortText();
    try
    {
      UploadLogReq ulr = new UploadLogReq(logHeader, body);// 获得设备请求包

      UploadLogResp rdsp = new UploadLogResp((short) body.getSequenceId());

      // 1.去重处理
      // 对于第一次发上来的消息，直接进库
      if (ulr.getSendCount() == 1)
      {
        saveRequestPacketInDB(ulr, currChannelId, ulr.getSendCount());
        sendPacket(ctx.channel(), rdsp);
        log.debug(logHeader + "[应答]收到并保存设备上传日志信息。");
      }
      else
      {
        // 检查该设备在最近的24小时内，是否有记录。如果有，就认为是重复的，如果没有，就进库。因为有可能第一次发的数据确实是没收到。第2次发才收到。所以需要进库处理。
        HashMap<String, Object> paraMap = new HashMap<String, Object>();
        paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());
        paraMap.put("sequenceId", body.getSequenceId());
        DeviceReceivePacketHistoryService service = (DeviceReceivePacketHistoryService) ApplicationContextUtil.getBeanById("deviceReceivePacketHistoryService");
        List<DeviceReceivePacketHistoryEntity> list = service.listByMap(paraMap);
        if (list.size() == 0)
        {
          saveRequestPacketInDB(ulr, currChannelId, ulr.getSendCount());
          log.debug(logHeader + "[应答]收到并保存重发的设备上传日志信息。当前重发次数：" + body.getSendCount());
        }
        else
        {
          log.debug(logHeader + "[应答]收到重发的设备上传日志信息，本次略过处理。当前重发次数：" + body.getSendCount());
        }
        sendPacket(ctx.channel(), rdsp);
      }
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 收到设备状态报告请求 要进行去重处理
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeReportDeviceStatusReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    String currChannelId = ctx.channel().id().asShortText();
    try
    {
      ReportDeviceStatusReq rdsr = new ReportDeviceStatusReq(logHeader, body);// 获得设备请求包

      ReportDeviceStatusResp rdsp = new ReportDeviceStatusResp((short) body.getSequenceId());

      // 1.去重处理
      // 对于第一次发上来的消息，直接进库
      if (rdsr.getSendCount() == 1)
      {
        saveRequestPacketInDB(rdsr, currChannelId, rdsr.getSendCount());
        // 2.刷新设备在redis里的状态
        SocketChannelMap.refreshDeviceStatus(currDeviceInfoEntity.getDeviceImei(), rdsr.getChannelIndex(), (byte) rdsr.getChannelStatus());
        sendPacket(ctx.channel(), rdsp);
        log.debug(logHeader + "[应答]收到并保存设备状态上报信息。状态回调数据已加入队列。");
        // 将需要回调的数据，放入发送队列
        offerQueue(rdsr);
      }
      else
      {
        // 检查该设备在最近的24小时内，是否有记录。如果有，就认为是重复的，如果没有，就进库。因为有可能第一次发的数据确实是没收到。第2次发才收到。所以需要进库处理。
        HashMap<String, Object> paraMap = new HashMap<String, Object>();
        paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());
        paraMap.put("sequenceId", body.getSequenceId());
        DeviceReceivePacketHistoryService service = (DeviceReceivePacketHistoryService) ApplicationContextUtil.getBeanById("deviceReceivePacketHistoryService");
        List<DeviceReceivePacketHistoryEntity> list = service.listByMap(paraMap);
        if (list.size() == 0)
        {
          saveRequestPacketInDB(rdsr, currChannelId, rdsr.getSendCount());
          // 2.刷新设备在redis里的状态
          SocketChannelMap.refreshDeviceStatus(currDeviceInfoEntity.getDeviceImei(), rdsr.getChannelIndex(), (byte) rdsr.getChannelStatus());
          log.debug(logHeader + "[应答]收到并保存重发的设备状态上报信息。当前重发次数：" + body.getSendCount() + "，状态回调数据已加入队列。");
          // 将需要回调的数据，放入发送队列
          offerQueue(rdsr);
        }
        else
        {
          log.debug(logHeader + "[应答]收到重发的设备状态上报信息，本次略过处理。当前重发次数：" + body.getSendCount());
        }
        sendPacket(ctx.channel(), rdsp);
      }
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 将需要回调的数据，放入发送队列
   * 
   * @param logHeader
   * @param rdsr
   */
  private void offerQueue(ReportDeviceStatusReq rdsr)
  {
    // 3.向请求方发起回调请求，进队列做重发(向app控制系统发回调消息)
    String httpUrl = currOrgInfoEntity.getCallUrl();
    String reqJson = SocketChannelMap.getCallBackPara(currDeviceInfoEntity.getDeviceImei(), rdsr.getChannelIndex(), rdsr.getChannelStatus(), DictEnum.CallBackType.STATUS_REPORT);

    CallBackTask callBackTask = new CallBackTask(httpUrl, reqJson);
    boolean result = TaskQueue.offerCallBackTask(callBackTask, true);

    log.debug(logHeader + "[应答]回调数据已加入队列。进队列结果：" + result);
  }

  /**
   * 获得服务器列表地址
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeGetServerListReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      GetServerListReq gdpr = new GetServerListReq(logHeader, body);// 获得设备请求包

      DeviceModelService deviceModelService = (DeviceModelService) ApplicationContextUtil.getBeanById("deviceModelService");
      HashMap<String, Object> paraMap = new HashMap<String, Object>();
      paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());

      DeviceModelEntity deviceModel = deviceModelService.getByMap(paraMap, "getByDeviceIMEI");

      // 生成应答包
      GetServerListResp gslr = new GetServerListResp((short) body.getSequenceId());
      gslr.setSendCount(body.getSendCount());
      gslr.setServerList(deviceModel.getDeviceServer());

      // 发送消息
      log.debug(logHeader + "[应答]返回服务器列表地址。");
      sendPacket(ctx.channel(), gslr);
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 获得调试服务器列表地址
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeGetDebugServerListReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      GetDebugServerListReq gdpr = new GetDebugServerListReq(logHeader, body);// 获得设备请求包

      DeviceModelService deviceModelService = (DeviceModelService) ApplicationContextUtil.getBeanById("deviceModelService");
      HashMap<String, Object> paraMap = new HashMap<String, Object>();
      paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());

      DeviceModelEntity deviceModel = deviceModelService.getByMap(paraMap, "getByDeviceIMEI");

      // 生成应答包
      GetDebugServerListResp gslr = new GetDebugServerListResp((short) body.getSequenceId());
      gslr.setSendCount(body.getSendCount());
      gslr.setServerList(deviceModel.getDeviceDebugServer());

      // 发送消息
      log.debug(logHeader + "[应答]返回调试服务器列表地址。");
      sendPacket(ctx.channel(), gslr);
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 获取设备参数请求
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeGetDeviceParaReq(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      GetDeviceParaReq gdpr = new GetDeviceParaReq(logHeader, body);// 获得设备请求包
      DeviceInfoService deviceInfoService = (DeviceInfoService) ApplicationContextUtil.getBeanById("deviceInfoService");
      HashMap<String, Object> paraMap = new HashMap<String, Object>();
      paraMap.put("deviceMac", gdpr.getDeviceMac());
      paraMap.put("deviceSn", gdpr.getDeviceSn());
      paraMap.put("simId", gdpr.getSimId());
      paraMap.put("currentHardVer", gdpr.getHardVer());
      paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());
      deviceInfoService.updateByMap(paraMap, "updateByMap");

      DeviceInfoEntity deviceInfo = deviceInfoService.getByKey(currDeviceInfoEntity.getDeviceImei());
      // 生成应答包
      GetDeviceParaResp gdResp = new GetDeviceParaResp((short) body.getSequenceId());
      gdResp.setSendCount(body.getSendCount());
      gdResp.setAnswerTimeOut((byte) deviceInfo.getAnswerTimeout().intValue());
      gdResp.setDeviceTimeOut((byte) deviceInfo.getDeviceTimeout().intValue());
      gdResp.setHeartInterval((byte) deviceInfo.getHeartInterval().intValue());
      gdResp.setReSendMode((byte) deviceInfo.getResendMode().intValue());
      gdResp.setRetryCount((byte) deviceInfo.getRetryCount().intValue());
      gdResp.setSocketMode((byte) deviceInfo.getSocketMode().intValue());
      if (deviceInfo.getPulseType() != null)
      {
        gdResp.setPulseType((byte) deviceInfo.getPulseType().intValue());
      }
      if (deviceInfo.getStartLevel() != null)
      {
        gdResp.setStartLevel((byte) deviceInfo.getStartLevel().intValue());
      }
      // 发送消息
      log.debug(logHeader + "[应答]返回设备参数。");
      sendPacket(ctx.channel(), gdResp);
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 收到设备执行启动指令的回执
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeStartDeviceResp(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      StartDeviceResp udpr = new StartDeviceResp(logHeader, body);// 获得设备应答包
      // 收入重发集合里，发送线程需要判断是否要进行重发。
      ReSendResponseInfo.addResponseInfo(currDeviceInfoEntity.getDeviceImei(), udpr.getSequenceId());

      // 不需要给设备应答。因为这个是被动接收的请求。

      // 向请求方发起回调请求，进队列做重发(向app控制系统发回调消息)
      String httpUrl = currOrgInfoEntity.getCallUrl();
      String reqJson = SocketChannelMap.getCallBackPara(currDeviceInfoEntity.getDeviceImei(), udpr.getChannelIndex(), udpr.getStartResult(), DictEnum.CallBackType.START_RESULT);

      CallBackTask callBackTask = new CallBackTask(httpUrl, reqJson);
      boolean result = TaskQueue.offerCallBackTask(callBackTask, true);

      log.debug(logHeader + "[应答]收到设备执行启动指令回执，回调数据已加入队列。进队列结果：" + result);
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 设备重启应答
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeRebootDeviceResp(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      RebootDeviceResp udpr = new RebootDeviceResp(logHeader, body);// 获得设备应答包

      // 不需要给设备应答。因为这个是被动接收的请求。
      // 暂时未实现回调功能

      log.debug(logHeader + "[应答]收到设备重启应答：" + udpr.toString());
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 执行要求设备更新参数应答
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeUpdateDeviceParaResp(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      UpdateDeviceParaResp udpr = new UpdateDeviceParaResp(logHeader, body);// 获得设备应答包
      // 把收到的消息放入队列，由后续线程处理
      addReceiveQueue(udpr, currDeviceInfoEntity, ctx.channel());
      // 不需要给设备应答。因为这个是被动接收的请求。
      log.debug(logHeader + "[应答]执行要求设备更新参数应答：" + udpr.toString());
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 执行获得设备应答消息
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeSetUpload(String channelId, SocketPacket body) throws Exception
  {
    // try
    // {
    // SetUploadLogResp setUploadLogResp = new SetUploadLogResp(logHeader,
    // body);// 获得设备应答包
    // // 把收到的消息放入队列，由后续线程处理
    // addReceiveQueue(setUploadLogResp,
    // SocketChannelMap.getDeviceByChannelId(channelId),
    // SocketChannelMap.getChannelById(channelId));
    // }
    // catch (Exception e)
    // {
    // throw e;
    // }
  }

  /**
   * 执行投币处理后台需要做去重，设备需要做重发
   * 这里直接做数据库处理。原因在于，如果进队列处理，万一队列处理插入数据库出问题或者服务器重启，那么所有的投币数据全部丢失。
   * 而这样做的好处在于，如果服务器的数据库没有正确的处理，设备将不会收到投币的应答。此时设备可以做重发处理。
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executePutCoin(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    String currChannelId = ctx.channel().id().asShortText();
    try
    {
      PutCoinReq putCoinReq = new PutCoinReq(logHeader, body);// 获得请求包

      PutCoinResp putCoinResp = new PutCoinResp((short) body.getSequenceId());// 返回消息包
      putCoinResp.setSendCount(body.getSendCount());

      // 对于第一次发上来的消息，直接进库
      if (putCoinReq.getSendCount() == 1)
      {
        saveRequestPacketInDB(putCoinReq, currChannelId, putCoinReq.getSendCount());
        // 把需要回应的数据包放入队列，由后续线程处理

        sendPacket(ctx.channel(), putCoinResp);
        log.debug(logHeader + "[应答]收到并保存投币消息。");
      }
      else
      {
        // 检查该设备在最近的24小时内，是否有记录。如果有，就认为是重复的，如果没有，就进库。因为有可能第一次发的数据确实是没收到。第2次发才收到。所以需要进库处理。
        HashMap<String, Object> paraMap = new HashMap<String, Object>();
        paraMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());
        paraMap.put("sequenceId", body.getSequenceId());
        DeviceReceivePacketHistoryService service = (DeviceReceivePacketHistoryService) ApplicationContextUtil.getBeanById("deviceReceivePacketHistoryService");
        List<DeviceReceivePacketHistoryEntity> list = service.listByMap(paraMap);
        if (list.size() == 0)
        {
          saveRequestPacketInDB(putCoinReq, currChannelId, putCoinReq.getSendCount());
          log.debug(logHeader + "[应答]收到并保存重发的投币消息。当前重发次数：" + body.getSendCount());
        }
        else
        {
          log.debug(logHeader + "[应答]收到重发的投币消息，本次略过处理。当前重发次数：" + body.getSendCount());
        }
        sendPacket(ctx.channel(), putCoinResp);
      }
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 把发上来的请求保存到数据库
   * 
   * @param body
   * @param currChannelId
   * @param sendCount
   */
  private void saveRequestPacketInDB(BasePacket body, String currChannelId, int sendCount)
  {
    DeviceReceivePacketHistoryService service = (DeviceReceivePacketHistoryService) ApplicationContextUtil.getBeanById("deviceReceivePacketHistoryService");
    DeviceReceivePacketHistoryEntity drphe = new DeviceReceivePacketHistoryEntity();
    drphe.setChannelId(currChannelId);
    drphe.setCommandId(new Integer(body.getCommandId()));
    drphe.setCommandName(body.getPacketName());
    drphe.setDeviceImei(currDeviceInfoEntity.getDeviceImei());
    drphe.setPacketContent(ByteHelper.byteToHexString(body.getPacketBody()));
    drphe.setPacketData(body.toString());
    drphe.setPacketLength(body.getTotalLength());
    drphe.setReceiveTime(new Date());
    drphe.setExecuteTime(new Date());
    drphe.setSequenceId(body.getSequenceId());
    drphe.setSendCount(sendCount);
    drphe.setServerId(ServerConfig.SERVIER_ID);
    service.insert(drphe);
  }

  /**
   * 修改设备密码 本请求的应答不做重发处理。
   * 
   * @param ctx
   * @param body
   * @throws Exception
   */
  private void editDevicePassword(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      EditDevicePasswordReq editDevicePasswordReq = new EditDevicePasswordReq(logHeader, body);// 获得请求包
      // 校验密码，不能是空，长度不能错，不能是出厂密码
      if (editDevicePasswordReq.getPassword() == null || editDevicePasswordReq.getPassword().length() != 4 || "0000".equals(editDevicePasswordReq.getPassword()))
      {
        // 生成应答包
        EditDevicePasswordResp editDevicePasswordResp = new EditDevicePasswordResp((short) body.getSequenceId());// 返回消息包
        editDevicePasswordResp.setSendCount(body.getSendCount());
        editDevicePasswordResp.setResult(DictEnum.EditPasswordResultCode.PASSWORD_RULE_ERROR);
        sendPacket(ctx.channel(), editDevicePasswordResp);
        log.debug(logHeader + "[应答]发送修改密码应答。密码修改失败，新密码不能和出厂密码相同。");
        return;
      }
      // 校验密码，必须是数字、大小写字母，不能是其他字符
      if (RandomNumHelper.checkPassword(editDevicePasswordReq.getPassword()) == false)
      {
        // 生成应答包
        EditDevicePasswordResp editDevicePasswordResp = new EditDevicePasswordResp((short) body.getSequenceId());// 返回消息包
        editDevicePasswordResp.setSendCount(body.getSendCount());
        editDevicePasswordResp.setResult(DictEnum.EditPasswordResultCode.PASSWORD_RULE_ERROR);
        sendPacket(ctx.channel(), editDevicePasswordResp);
        log.debug(logHeader + "[应答]发送修改密码应答。密码修改失败，新密码的规则错误。");
        return;
      }
      if (isLock == false)
      {
        // 生成应答包
        EditDevicePasswordResp editDevicePasswordResp = new EditDevicePasswordResp((short) body.getSequenceId());// 返回消息包
        editDevicePasswordResp.setSendCount(body.getSendCount());
        editDevicePasswordResp.setResult(DictEnum.EditPasswordResultCode.DENY_EDIT_PASSWORD);
        sendPacket(ctx.channel(), editDevicePasswordResp);
        log.debug(logHeader + "[应答]发送修改密码应答。密码修改失败，已经解锁的设备不允许使用修改密码功能。");
        return;
      }

      // 更新密码
      DeviceInfoService deviceInfoService = (DeviceInfoService) ApplicationContextUtil.getBeanById("deviceInfoService");
      HashMap<String, Object> paramMap = new HashMap<String, Object>();
      paramMap.put("deviceImei", currDeviceInfoEntity.getDeviceImei());
      paramMap.put("devicePassword", editDevicePasswordReq.getPassword());
      long result = deviceInfoService.updateByMap(paramMap, "updatePassByIMEI");

      if (result > 0)
      {
        // 生成应答包
        EditDevicePasswordResp editDevicePasswordResp = new EditDevicePasswordResp((short) body.getSequenceId());// 返回消息包
        editDevicePasswordResp.setSendCount(body.getSendCount());
        editDevicePasswordResp.setResult(DictEnum.EditPasswordResultCode.SUCCESS);
        sendPacket(ctx.channel(), editDevicePasswordResp);
        isLock = false;
        log.debug(logHeader + "[应答]发送修改密码应答，密码修改成功。设备已解锁。");
      }
      else
      {
        // 生成应答包
        EditDevicePasswordResp editDevicePasswordResp = new EditDevicePasswordResp((short) body.getSequenceId());// 返回消息包
        editDevicePasswordResp.setSendCount(body.getSendCount());
        editDevicePasswordResp.setResult(DictEnum.EditPasswordResultCode.UNKNOW_ERROR);
        sendPacket(ctx.channel(), editDevicePasswordResp);
        log.debug(logHeader + "[应答]发送修改密码应答，密码修改失败。");
      }
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 执行心跳处理 本请求的应答不做重发处理。
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeHeart(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      HeartReq heartReq = new HeartReq(logHeader, body);// 获得请求包

      // 生成应答包
      HeartResp heartResp = new HeartResp((short) body.getSequenceId());// 返回消息包
      heartResp.setSendCount(body.getSendCount());
      sendPacket(ctx.channel(), heartResp);

      log.debug(logHeader + "[应答]发送心跳应答。");
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 执行心跳处理 本请求的应答不做重发处理。
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeHeartBySignal(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      HeartBySignalReq heartReq = new HeartBySignalReq(logHeader, body);// 获得请求包

      // 生成应答包
      HeartBySignalResp heartResp = new HeartBySignalResp((short) body.getSequenceId());// 返回消息包
      heartResp.setSendCount(body.getSendCount());
      sendPacket(ctx.channel(), heartResp);
      //在redis里记录信号强度，方便app上查询
      SocketChannelMap.updateDeviceSignal(currDeviceInfoEntity.getDeviceImei(), heartReq.getSignal());
      log.debug(logHeader + "[应答]发送带信号强度的心跳应答。");
    }
    catch (Exception e)
    {
      throw e;
    }
  }
  
  /**
   * 退出登录处理 本请求的应答不做重发处理。
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private void executeLoginOut(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      LoginOutReq loginOutReq = new LoginOutReq(logHeader, body);// 获得请求包

      // 生成退出登录应答
      LoginOutResp loginOutResp = new LoginOutResp(DictEnum.ResultCode.SUCCESS, (short) body.getSequenceId());// 退出登录应答包
      loginOutResp.setSendCount(body.getSendCount());

      sendPacket(ctx.channel(), loginOutResp);
      log.debug(logHeader + "[应答]发送退出登录应答。");
      ctx.close();
      return;
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 收到登录请求后的处理 本请求的应答不做重发处理。 做同步处理，禁止同一时间发2个登录请求上来。
   * 
   * @param ctx
   * @param logHeader
   * @param body
   * @throws Exception
   */
  private synchronized void executeLogin(ChannelHandlerContext ctx, SocketPacket body) throws Exception
  {
    try
    {
      LoginReq loginReq = new LoginReq(logHeader, body);// 将数据包转换为请求包
      LoginResp loginResp = new LoginResp();// 待返回的应答包
      loginResp.setSendCount(body.getSendCount());

      String currChannelId = ctx.channel().id().asShortText();

      // 校验登录参数
      if (loginReq == null || StringUtils.isEmpty(loginReq.getLoginName()) || StringUtils.isEmpty(loginReq.getDevicePass()))
      {
        LoginOutResp loginOutResp = new LoginOutResp(DictEnum.LoginResult.PARA_ERROR, (short) body.getSequenceId());// 退出登录应答包
        sendPacket(ctx.channel(), loginOutResp);
        log.debug(logHeader + "[应答]登录参数错误，断开连接。");
        ctx.close();
      }

      // 查询登录密码，验证登录
      DeviceInfoService deviceInfoService = (DeviceInfoService) ApplicationContextUtil.getBeanById("deviceInfoService");
      // 保存该设备属于哪一个商户的信息
      OrgInfoService orgInfoService = (OrgInfoService) ApplicationContextUtil.getBeanById("orgInfoService");

      String currLoinName = null;
      switch (loginReq.getLoginType())
      {
        case DictEnum.LoginType.IMEI_LOGIN:
          currDeviceInfoEntity = deviceInfoService.getByKey(loginReq.getLoginName());
          currOrgInfoEntity = orgInfoService.getByKey(loginReq.getLoginName());
          if (currDeviceInfoEntity != null && currDeviceInfoEntity.getDeviceImei() != null)
          {
            currLoinName = currDeviceInfoEntity.getDeviceImei();
          }
          break;
        case DictEnum.LoginType.MAC_LOGIN:
          HashMap<String, Object> loginMap = new HashMap<String, Object>();
          loginMap.put("deviceMac", loginReq.getLoginName());
          currDeviceInfoEntity = deviceInfoService.getByMap(loginMap, "getByMac");
          currOrgInfoEntity = orgInfoService.getByMap(loginMap, "getByMac");
          if (currDeviceInfoEntity != null && currDeviceInfoEntity.getDeviceMac() != null)
          {
            currLoinName = currDeviceInfoEntity.getDeviceMac();
          }
          break;
        case DictEnum.LoginType.SN_LOGIN:
          loginMap = new HashMap<String, Object>();
          loginMap.put("deviceSn", loginReq.getLoginName());
          currDeviceInfoEntity = deviceInfoService.getByMap(loginMap, "getBySn");
          currOrgInfoEntity = orgInfoService.getByMap(loginMap, "getBySn");
          if (currDeviceInfoEntity != null && currDeviceInfoEntity.getDeviceSn() != null)
          {
            currLoinName = currDeviceInfoEntity.getDeviceSn();
          }
          break;
        default:
          currDeviceInfoEntity = null;
          currOrgInfoEntity = null;
          currLoinName = null;
          break;
      }

      if (currDeviceInfoEntity == null || currOrgInfoEntity == null || currLoinName == null)
      {
        // 设备不存在，或者是对应的商户信息没有
        loginResp.setCommandId(DeviceCommand.LOGIN_RESP);
        loginResp.setSequenceId((short) body.getSequenceId());
        loginResp.setLoginResult(DictEnum.LoginResult.USERNAME_OR_PASSWORD_ERROR);
        sendPacket(ctx.channel(), loginResp);
        log.debug(logHeader + "[应答]登录失败，设备不存在，断开连接。");
        ctx.close();
        return;
      }
      if (StringUtils.isEmpty(currDeviceInfoEntity.getDevicePassword()))
      {
        // 判断第一次登录是否用的初始密码，前面已经判断过是否为空。所以这里不用再判断了
        if (!"0000".equals(loginReq.getDevicePass()))
        {
          // 如果第一次登录，不是初始密码，则返回登录失败
          loginResp.setCommandId(DeviceCommand.LOGIN_RESP);
          loginResp.setSequenceId((short) body.getSequenceId());
          loginResp.setLoginResult(DictEnum.LoginResult.USE_DEFAULT_LOGIN_PASSWORD);
          sendPacket(ctx.channel(), loginResp);
          log.debug(logHeader + "[应答]登录失败，第一次登录时，请使用默认密码：0000登录，断开连接。");
          ctx.close();
          return;
        }
        else
        {
          // 第一登录成功
          currDeviceInfoEntity.setDevicePassword("0000");
        }
      }
      if (loginReq.getLoginName().equals(currLoinName) && loginReq.getDevicePass().equals(currDeviceInfoEntity.getDevicePassword()))
      {
        // 先找到这个设备以前的链路，如果有的话，要先删除掉。只允许最后一个设备登录
        Channel sc = SocketChannelMap.getSocketChannelByIMEI(currDeviceInfoEntity.getDeviceImei());

        // 如果是同一个链路，则不会把自己踢下线
        if (sc != null && !currChannelId.equals(sc.id().asShortText()))
        {
          // 断开前一个链路
          LoginOutResp loginOutResp = new LoginOutResp(DictEnum.ResultCode.DUPLICATE_LINK_ERROR, (short) body.getSequenceId());// 退出登录应答包
          sendPacket(sc, loginOutResp);
          log.debug(logHeader + "[应答]重复的链路错误，断开前一个链路的连接。");
          sc.close();// 断开以前的链路，不是当前链路
        }

        // 登录成功
        if ("0000".equals(currDeviceInfoEntity.getDevicePassword()))
        {
          // 第一次登录成功，要求设备修改登录密码
          // 设备自己设置的随机密码不允许为0000，这个在修改密码接口做限制了。
          loginResp.setLoginResult(DictEnum.LoginResult.UPDATE_LOGIN_PASSWORD);
        }
        else
        {
          // 如果不是第一次登录，则解锁
          isLock = false;
          loginResp.setLoginResult(DictEnum.LoginResult.SUCCESS);
        }
        loginResp.setServerTime(TimeHelper.getCurrSecond());
        loginResp.setCommandId(DeviceCommand.LOGIN_RESP);
        loginResp.setDebugMode((byte) currDeviceInfoEntity.getDebugMode().intValue());// 设备工作在调试状态还是运行状态
        loginResp.setSequenceId(loginReq.getSequenceId());// 把登录请求里的序号返回给应答

        sendPacket(ctx.channel(), loginResp);// 发送登录成功数据包

        // 把当前的连接放入集合中备用
        SocketChannelMap.add(currDeviceInfoEntity.getDeviceImei(), currDeviceInfoEntity.getDeviceChannelNum(), loginReq.getDeviceType(), ctx.channel());

        log.debug(logHeader + "[应答]登录成功。");
      }
      else
      {
        loginResp.setCommandId(DeviceCommand.LOGIN_RESP);
        loginResp.setSequenceId(loginReq.getSequenceId());
        loginResp.setLoginResult(DictEnum.LoginResult.USERNAME_OR_PASSWORD_ERROR);
        sendPacket(ctx.channel(), loginResp);
        log.debug(logHeader + "[应答]登录失败，设备不存在,或者密码错误，断开连接。");
        ctx.close();
      }
    }
    catch (Exception e)
    {
      throw e;
    }
  }

  /**
   * 把收到的数据放入队列进行后续处理
   * 
   * @param basePacket
   * @param deviceEntity
   * @param socketChannel
   */
  private void addReceiveQueue(BasePacket basePacket, DeviceInfoEntity deviceInfoEntity, Channel socketChannel)
  {
    String channelId = socketChannel.id().asShortText();
    String remoteIpAddress = ((InetSocketAddress) socketChannel.remoteAddress()).getAddress().getHostAddress();
    String logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]" + basePacket.getPacketName();

    // 把收到的数据放入队列，由后续线程处理
    ReceiveTask rt = new ReceiveTask(basePacket, deviceInfoEntity);
    if (TaskQueue.offerReceiveTask(rt, true))
    {
      log.debug(logHeader + "[应答]已进入接收队列处理。");
    }
    else
    {
      log.debug(logHeader + "[应答]进入接收队列处理失败，队列已满。");
    }
  }

  // /**
  // * 将要发送的指令放入发送队列，由后续线程处理
  // *
  // * @param basePacket
  // * @param deviceEntity
  // * @param socketChannel
  // */
  // private void addSendQueue(BasePacket basePacket, DeviceInfoEntity
  // deviceInfoEntity, Channel socketChannel)
  // {
  // String channelId = socketChannel.id().asShortText();
  // String remoteIpAddress = ((InetSocketAddress)
  // socketChannel.remoteAddress()).getAddress().getHostAddress();
  // String logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]" +
  // basePacket.getPacketName();
  //
  // // 把收到的数据放入队列，由后续线程处理
  // SendTask st = new SendTask(basePacket, deviceInfoEntity);
  // if (TaskQueue.offerSendTask(st, true))
  // {
  // log.debug(logHeader + "已进入发送队列处理。");
  // }
  // else
  // {
  // log.debug(logHeader + "进入发送队列处理失败，队列已满。");
  // }
  // }

  /**
   * 发送数据包 当没办法提交给队列时使用
   * 
   * @param ctx
   * @param packet
   */
  private void sendPacket(Channel channel, BasePacket packet)
  {
    channel.writeAndFlush(packet).addListener(new ChannelFutureListener()
    {
      // 数据发送完毕后，触发本事件
      @Override
      public void operationComplete(ChannelFuture future) throws Exception
      {
        if (currDeviceInfoEntity != null && currOrgInfoEntity != null)
        {
          log.debug(logHeader + packet.getPacketName() + "[应答]向设备<" + currDeviceInfoEntity.getDeviceImei() + ">发送数据成功，机构代码<" + currOrgInfoEntity.getOrgId() + ">。发送内容：" + packet.toString());
        }
        else
        {
          log.debug(logHeader + packet.getPacketName() + "[应答]向设备发送数据成功。发送内容：" + packet.toString());
        }
      }
    });
  }

}
